package com.freesky.common.core.redis;

import com.freesky.common.utils.uuid.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

@Component
public class RedisLockUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    //redis的原始连接对象
    private RedisConnection connection;

    //加锁的脚本签名
    private String lockLuaSha;

    //添加分布式锁的lua脚本
    private String lockLua = "local lockName = KEYS[1]\n" +
            "local lockValue = ARGV[1]\n" +
            "local timeout = ARGV[2]\n" +
            "local flag = redis.call('setnx', lockName, lockValue)\n" +
            "\n" +
            "if flag == 1 then\n" +
            "\tredis.call('expire', lockName, timeout)\n" +
            "\treturn 1\n" +
            "end\n" +
            "\n" +
            "return 0";

    private String unLockLuaSha;

    private String unLockLua =
            "local lockName = KEYS[1]\n" +
                    "local lockValue = ARGV[1]\n" +
                    "\n" +
                    "local rValue = redis.call(\"get\", lockName)\n" +
                    "if rValue == lockValue then\n" +
                    "   redis.call('del', lockName)\n" +
                    "   return 1\n" +
                    "end\n" +
                    "\n" +
                    "return 0";

    private ThreadLocal<String> value = new ThreadLocal<>();

    /**
     * @PostConstruct - 添加的方法会在当前类的构造方法之后，并且在注入了成员变量之后执行
     */
    @PostConstruct
    private void init() {
        connection = redisTemplate.getConnectionFactory().getConnection();
        //缓存lua脚本
        lockLuaSha = connection.scriptLoad(lockLua.getBytes());
        unLockLuaSha = connection.scriptLoad(unLockLua.getBytes());
    }

    public void reLoadInit() {
        //缓存lua脚本
        lockLuaSha = connection.scriptLoad(lockLua.getBytes());
        unLockLuaSha = connection.scriptLoad(unLockLua.getBytes());
    }


    /**
     * 上锁的方法
     *
     * @return
     */
    public boolean lock(String name, long timeout) {
        String uuid = UUID.randomUUID().toString();
        value.set(uuid);

        //使用脚本加锁
        Long flag = connection.evalSha(lockLuaSha, ReturnType.INTEGER,
                1, name.getBytes(), uuid.getBytes(), (timeout + "").getBytes());

        return flag == 1;
    }

    public String lockEx(String name, long timeout) {
        String uuid = UUID.randomUUID().toString();
        //value.set(uuid);

        //使用脚本加锁
        Long flag = connection.evalSha(lockLuaSha, ReturnType.INTEGER,
                1, name.getBytes(), uuid.getBytes(), (timeout + "").getBytes());
        //判断是否上锁成功
        AtomicBoolean locked = new AtomicBoolean(Boolean.FALSE);
        Optional.ofNullable(flag).ifPresent(l -> locked.set(Long.valueOf(1L).equals(l)));
        if (!locked.get()) {
            return null;
        }else {
            return uuid;
        }
        //return uuid;
    }

    /**
     * 解锁的方法
     *
     * @return
     */
    public boolean unLock(String name) {
        String uuid = value.get();
        Long result = connection.evalSha(unLockLuaSha, ReturnType.INTEGER, 1,
                name.getBytes(), uuid.getBytes());
        return result == 1;
    }

    /**
     * 解锁的方法
     *
     * @return
     */
    public boolean unLockEx(String name,String value) {
        String uuid = value;
        Long result = connection.evalSha(unLockLuaSha, ReturnType.INTEGER, 1,
                name.getBytes(), uuid.getBytes());
        return result == 1;
    }
}
